package org.bjf.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.bjf.cache.CacheNameConfig.LocalCacheEnum;
import org.bjf.cache.CacheNameConfig.RedisCacheEnum;
import org.bjf.cache.LiveCaffeineCache;
import org.bjf.cache.RedisCache;
import org.bjf.cache.SecondaryCacheManager;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@SpringBootConfiguration
@EnableCaching
@Slf4j
public class CacheConfig extends CachingConfigurerSupport {

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(":").append(method.getName());
            for (Object obj : params) {
                sb.append(":").append(obj.toString());
            }
            return sb.toString();
        };
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 本地缓存 Caffeine Cache
     */
    @Bean
    public CacheManager caffeineCacheManager() {
        ArrayList<CaffeineCache> caches = new ArrayList<>();
        for (LocalCacheEnum cache : LocalCacheEnum.values()) {
            LiveCaffeineCache liveCaffeineCache = new LiveCaffeineCache(cache.name(),
                    Caffeine.newBuilder().recordStats()
                            .expireAfterWrite(cache.getTimeout(), TimeUnit.SECONDS)
                            .maximumSize(cache.getCapacity())
                            .build());
            caches.add(liveCaffeineCache);
        }
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(caches);
        cacheManager.afterPropertiesSet();
        return cacheManager;
    }

    /**
     * redis cache
     */
    @Bean
    public CacheManager redisCacheManager(RedisTemplate redisTemplate) {

        ArrayList<RedisCache> caches = new ArrayList<>();
        //取自定义的缓存值
        for (RedisCacheEnum cache : RedisCacheEnum.values()) {
            caches.add(new RedisCache(redisTemplate, cache.name(), cache.getTimeout()));
        }
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(caches);

        return cacheManager;
    }

    /**
     * 自定义二级缓存 caffeine + redis
     */
    @Bean
    public CacheManager secondaryCacheManager(RedisTemplate redisTemplate) {
        return new SecondaryCacheManager(redisTemplate);
    }

    @Bean
    @Primary
    public CompositeCacheManager compositeCacheManager(CacheManager caffeineCacheManager,
                                                       CacheManager redisCacheManager, CacheManager secondaryCacheManager) {
        CompositeCacheManager cacheManager = new CompositeCacheManager();
        cacheManager
                .setCacheManagers(
                        Arrays.asList(caffeineCacheManager, redisCacheManager, secondaryCacheManager));
        cacheManager.setFallbackToNoOpCache(Boolean.TRUE);
        return cacheManager;
    }

    @Override
    public CacheErrorHandler errorHandler() {
        return new LiveCacheErrorHandler();
    }

    /**
     * 缓存异常统一处理,读写缓存不可用的时候,保证业务正常
     */
    private static class LiveCacheErrorHandler implements CacheErrorHandler {

        @Override
        public void handleCacheGetError(RuntimeException ex, Cache cache, Object key) {
            log.error("读取缓存失败，name=" + cache.getName() + ",key=" + key, ex);
        }

        @Override
        public void handleCachePutError(RuntimeException ex, Cache cache, Object key, Object value) {
            log.error("写入缓存失败，name=" + cache.getName() + ",key=" + key, ex);
        }

        @Override
        public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
            throw exception;
        }

        @Override
        public void handleCacheClearError(RuntimeException exception, Cache cache) {
            throw exception;
        }
    }
}
